/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.auth.internal.kerberos.v5;

import java.io.IOException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.apache.harmony.security.asn1.ASN1Explicit;
import org.apache.harmony.security.asn1.ASN1Integer;
import org.apache.harmony.security.asn1.ASN1OctetString;
import org.apache.harmony.security.asn1.ASN1Sequence;
import org.apache.harmony.security.asn1.ASN1Type;
import org.apache.harmony.security.asn1.BerInputStream;

/**
 * @see http://www.ietf.org/rfc/rfc4120.txt
 */
public class EncryptedData {

	/**
	 * DES in CBC mode with CRC-based checksum
	 */
	public static final int DES_CBC_CRC = 1;

	/**
	 * DES in CBC mode with CRC-based checksum
	 */
	public static final int DES_CBC_MD4 = 2;

	/**
	 * DES in CBC mode with CRC-based checksum
	 */
	public static final int DES_CBC_MD5 = 3;

	private final int etype;

	private final int kvno;

	private final byte[] cipher;

	/**
	 * <pre>
	 * EncryptedData   ::= SEQUENCE {
	 *      etype   [0] Int32 -- EncryptionType --,
	 *      kvno    [1] UInt32 OPTIONAL,
	 *      cipher  [2] OCTET STRING -- ciphertext
	 *      }
	 * </pre>
	 */
	static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
			// TODO should we define Int32 type?
			new ASN1Explicit(0, ASN1Integer.getInstance()), // etype
			// TODO should we define UInt32 type?
			new ASN1Explicit(1, ASN1Integer.getInstance()), // kvno
			// cipher
			new ASN1Explicit(2, ASN1OctetString.getInstance()), }) {
		{
			setOptional(1); // kvno
		}

		@Override
		protected Object getDecodedObject(BerInputStream in) throws IOException {
			final Object[] values = (Object[]) in.content;
			return new EncryptedData(ASN1Integer.toIntValue(values[0]),
					ASN1Integer.toIntValue(values[1]), (byte[]) values[2]);
		}

		@Override
		protected void getValues(Object object, Object[] values) {
			throw new RuntimeException(); // FIXME message
		}
	};

	public EncryptedData(int etype, int kvno, byte[] cipher) {
		this.etype = etype;
		this.kvno = kvno;
		this.cipher = cipher;
	}

	public byte[] decrypt(SecretKey key) {

		int offset;

		IvParameterSpec initCipherState;
		switch (etype) {
		case DES_CBC_CRC:
			offset = 12;// confounder(8)+CRC-32 checksum(4)
			// copy of original key
			initCipherState = new IvParameterSpec(key.getEncoded());
			break;
		case DES_CBC_MD4:
		case DES_CBC_MD5:
			offset = 24;// confounder(8)+ MD4/5 checksum(16)
			// all-zero
			initCipherState = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0,
					0, 0, 0, });
			break;
		default:
			throw new RuntimeException();// FIXME not implemented yet
		}

		try {
			final Cipher dcipher = Cipher.getInstance("DES/CBC/NoPadding");

			dcipher.init(Cipher.DECRYPT_MODE, key, initCipherState);

			final byte[] tmp = dcipher.doFinal(cipher);

			// TODO: verify checksum

			// cat out: confounder and checksum bytes
			// TODO: how to do the same for padding bytes?
			final byte[] result = new byte[tmp.length - offset];
			System.arraycopy(tmp, offset, result, 0, result.length);

			return result;
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	public byte[] getCipher() {
		return cipher;
	}

	public int getEtype() {
		return etype;
	}

	public int getKvno() {
		return kvno;
	}
}
